package com.chen.base.utils;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.chen.root.annotation.OrderNum;
import com.chen.base.inter.MethodAnnotationInter;
import lombok.AllArgsConstructor;
import lombok.Data;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 方法-注解式-路由工具类
 * @author: Long
 * @date: 2023/11/1 20:47
 **/
public class AnnotationRouteUtil {

    /**
     * 从bean容器中获取所有存在指定注解的方法，
     * 并依据注解属性指定唯一匹配值，
     * {@link MethodAnnotationInter} 返回 true-匹配成功，反之false
     * @param annotationType 注解类型
     * @param classifier 注解属性
     * @param handler 外部逻辑，返回 true-匹配成功，反之false
     * @param excludes 需要排除的bean类对象
     * @param <A> 注解泛型
     * @param <K> 注解属性泛型
     * @throws Exception err
     */
    public static <A extends Annotation, K> void route(Class<A> annotationType, Function<? super A, ? extends K> classifier, K k,
                                                       MethodAnnotationInter<A> handler, Class<?>... excludes) throws Exception {
        Map<Object, Map<Method, A>> beansMap = SpringContextHolder.getBeansByMethodAnnotate(annotationType,excludes);
        uniqueAnnotation(beansMap,classifier,null);

        for (Object bean : beansMap.keySet()) {
            for (Map.Entry<Method, A> entry : beansMap.get(bean).entrySet()) {
                K apply = classifier.apply(entry.getValue());
                if (apply.equals(k)){
                    handler.execute(bean, entry.getKey(), entry.getValue());
                    return;
                }
            }
        }
        throw new Exception("执行器不存在!!! ");
    }

    /**
     * 判断bean集，所有的方法注解是否唯一
     * beansMap -> 可从{@link SpringContextHolder} getBeansByMethodAnnotate()获取
     * @param beansMap bean集
     * @param classifier 注解属性唯一性
     * @param errMsg 异常消息提示
     * @param <T> 注解类型
     * @param <K> 注解属性唯一性判断
     */
    public static <T extends Annotation, K> void uniqueAnnotation(Map<Object, Map<Method, T>> beansMap,
                                                                  Function<? super T, ? extends K> classifier,
                                                                  String errMsg){
        String finalErrMsg = StrUtil.isNotBlank(errMsg) ? errMsg : "重复 -> ";
        beansMap.values()
                .stream()
                .map(Map::values)
                .flatMap(Collection::stream)
                .collect(Collectors.groupingBy(classifier))
                .forEach((k, v) -> Assert.isTrue(v.size() == 1, finalErrMsg + v));
    }

    /**
     * 方法注解式链路
     * todo 链路节点存在返回值，则中止链路
     * @param annotationType 注解类型
     * @param params 方法参数
     * @param excludes 排除对应的bean
     * @param <A> 注解泛型
     * @throws Exception Exception
     */
    public static <A extends Annotation> Object link(Class<A> annotationType, Object params,
                                                     Class<?>... excludes) throws Exception {
        Map<Object, Map<Method, A>> beansMap = SpringContextHolder.getBeansByMethodAnnotate(annotationType, excludes);
        List<BeanMethodDTO> list = new ArrayList<>();
        for (Object bean : beansMap.keySet()) {
            for (Method method : beansMap.get(bean).keySet()) {
                list.add(new BeanMethodDTO(bean, method));
            }
        }
        // 排序
        list = StreamUtil.sort(list, obj -> {
            OrderNum orderNum = obj.getMethod().getAnnotation(OrderNum.class);
            return orderNum != null ? orderNum.value() : Integer.MAX_VALUE;
        }, true);

        for (BeanMethodDTO beanMethod : list) {
            Method method = beanMethod.getMethod();
            // 构建方法入参，如果第一个参数类型等于params类型，则传递实参进去
            int count = method.getParameterCount();
            Object[] args = new Object[count];
            if (params != null && count >= 1 && method.getParameterTypes()[0] == params.getClass()) {
                args[0] = params;
            }

            // 执行方法，如果返回值不为null，则中止链路
            Object invoke = method.invoke(beanMethod.getBean(), args);
            if (invoke != null){
                return invoke;
            }
        }
        return null;
    }

    @Data
    @AllArgsConstructor
    private static class BeanMethodDTO {

        /** bean */
        private Object bean;

        /** bean method */
        private Method method;

    }
}
